package com.own.component.log.operation.aspect;

import com.alibaba.fastjson2.JSON;
import com.own.component.client.base.RequestClientManager;
import com.own.component.common.exception.BusinessException;
import com.own.component.common.login.util.SessionUserUtil;
import com.own.component.common.model.ResultModel;
import com.own.component.common.util.IpUtil;
import com.own.component.common.util.JacksonJsonMapperUtil;
import com.own.component.common.util.RequestUtil;
import com.own.component.common.util.executor.ExecutorUtil;
import com.own.component.log.operation.aop.LogOperationAop;
import com.own.component.log.operation.core.ExtraLogOperationSpringHook;
import com.own.component.log.operation.core.PersistenceLogOperationSpringHook;
import com.own.component.log.operation.entity.LogOperationEntity;
import com.own.component.log.operation.property.LogOperationProperty;
import com.own.component.log.operation.util.LogOperationUtil;
import com.own.constant.ConstantCommon;
import com.own.constant.ConstantSystemAopOrder;
import com.own.constant.ConstantValue;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;

/**
 * LogOperationAspect
 *
 * @author chenxueli
 * @date 2023-07-31 19:34:00
 */
@Slf4j
@Aspect
@Order(ConstantSystemAopOrder.LOG_OPERATION)
@Component
@ConditionalOnProperty(prefix = ConstantValue.CONFIG_VALUE_CONTENT + "log.operation", name = "collect", havingValue = "true")
public class LogOperationAspect {

    private static final String LOG_OPERATION_ENTITY_KEY = "LOG_OPERATION_ENTITY_KEY";
    /**
     * 用于获取方法参数定义名字.
     */
    private static final DefaultParameterNameDiscoverer DEFAULT_PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
    @Resource
    private ExtraLogOperationSpringHook extraLogOperationSpringHook;
    @Resource
    private PersistenceLogOperationSpringHook persistenceLogOperationSpringHook;
    @Resource
    private HttpServletRequest request;
    @Resource
    private RequestClientManager requestClientManager;
    @Resource
    private LogOperationProperty logOperationProperty;
    @Resource
    private ExecutorUtil executorUtil;

    @Pointcut("@annotation(com.own.component.log.operation.aop.LogOperationAop)")
    public void aspect() {
    }

    @Before("aspect() && @annotation(data)")
    public void doBefore(JoinPoint point, LogOperationAop data) {
        // 获取当前登录的用户信息
        var loginUser = SessionUserUtil.getLoginUser(false);
        var signature = (MethodSignature) point.getSignature();
        // 获取请求方法
        var methodName = point.getTarget().getClass().getName() + "#" + signature.getName();
        var moduleName = LogOperationUtil.getModuleName(point, data);
        var parameterNames = DEFAULT_PARAMETER_NAME_DISCOVERER.getParameterNames(signature.getMethod());
        var requestParams = new HashMap<String, Object>();
        // 获取请求参数
        if (point.getArgs() != null && parameterNames != null) {
            var args = point.getArgs();
            for (int i = 0; i < args.length; i++) {
                var arg = args[i];
                var content = LogOperationUtil.resolveObject(arg);
                requestParams.put(parameterNames[i], content);
            }
        }
        var entity = new LogOperationEntity();
        // 执行其他的信息
        {
            var extra = new HashMap<String, Object>();
            var list = extraLogOperationSpringHook.get(data.type());
            list.forEach(item -> extra.put(item.name(), item.collect()));
            entity.setExtra(extra);
        }
        entity.setLevel(data.level());
        entity.setIp(IpUtil.getIpAddress(request));
        var client = requestClientManager.client(request);
        entity.setClient(StringUtils.isBlank(client) ? data.defaultClient() : client);
        entity.setLoginUser(loginUser);
        entity.setModuleName(moduleName);
        entity.setOperationName(LogOperationUtil.getOperationName(point, data));
        entity.setMethodName(methodName);
        entity.setHeaders(RequestUtil.headerForDecode(request));
        entity.setRequestUrl(request.getRequestURI());
        entity.setRequestMethod(request.getMethod());
        entity.setTraceId(MDC.get(ConstantCommon.TRACE_ID));
        entity.setRequest(new LogOperationEntity.ExtraRequestParam(LocalDateTime.now(), JSON.toJSONString(requestParams)));
        entity.setService(StringUtils.isBlank(data.service()) ? logOperationProperty.getServiceName() : data.service());
        // 将数据存入到request缓存中
        request.setAttribute(LOG_OPERATION_ENTITY_KEY, entity);
    }

    @AfterReturning(value = "aspect()", returning = "result")
    public void doAfterReturning(JoinPoint point, Object result) {
        // 获取已有的对象信息
        var entity = (LogOperationEntity) request.getAttribute(LOG_OPERATION_ENTITY_KEY);
        // 如果不是标准返回结果，直接返回
        if (!(result instanceof ResultModel<?> resultModel)) {
            // 直接保存通用的数据
            entity.setStatus(1);
            var map = new HashMap<String, Object>();
            map.put("data", JSON.toJSONString(result));
            entity.setResponse(new LogOperationEntity.ExtraResponseParam(LocalDateTime.now(), map));
            executorUtil.execute(() -> persistenceLogOperationSpringHook.run(entity));
            return;
        }
        // 获取返回值
        entity.setMessage(resultModel.getMessage());
        entity.setCode(resultModel.getCode());
        entity.setStatus(1);
        // 获取返回值
        var map = new HashMap<String, Object>();
        map.put("data", JacksonJsonMapperUtil.toJsonString(resultModel.getData()));
        entity.setResponse(new LogOperationEntity.ExtraResponseParam(LocalDateTime.now(), map));
        // 执行日志操作
        executorUtil.execute(() -> persistenceLogOperationSpringHook.run(entity));
    }

    @AfterThrowing(throwing = "throwable", pointcut = "aspect()")
    public void doAfterThrowing(Throwable throwable) {
        // 获取已有的对象信息
        var entity = (LogOperationEntity) request.getAttribute(LOG_OPERATION_ENTITY_KEY);
        // 获取返回值
        entity.setMessage(throwable.getMessage());
        entity.setStatus(2);
        var maxLength = 10;
        if (throwable instanceof BusinessException e) {
            entity.setCode(e.getCode());
            // 如果是自定义的异常，只获取前5行的堆栈日志
            maxLength = 5;
        }
        var length = Math.min(throwable.getStackTrace().length, maxLength);
        var list = new ArrayList<String>();
        for (var i = 0; i < length; i++) {
            list.add(throwable.getStackTrace()[i].toString());
        }
        var map = new HashMap<String, Object>();
        map.put("throwable", throwable.toString());
        map.put("detail", JSON.toJSONString(list));
        entity.setResponse(new LogOperationEntity.ExtraResponseParam(LocalDateTime.now(), map));
        // 执行日志操作
        executorUtil.execute(() -> persistenceLogOperationSpringHook.run(entity));
    }

}
